using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using HtmlAgilityPack;

namespace SecurityModule
{
    static class JavascriptManager
    {
        /// <summary>
        /// A list of trusted external script sources.
        /// </summary>
        public static readonly string[] WhitelistedScriptSources = new[] { "https://code.jquery.com", "https://cdnjs.cloudflare.com", "https://maxcdn.bootstrapcdn.com" };

        /// <summary>
        /// Cleans a string representing (valid) HTML code of all JS (inside a script tag or an HTML attribute starting with "on") expect for external JS from trusted sources, a list of trusted sources is stored in <see cref="JavascriptManager.WhitelistedScriptSources"/>. This method also supports JS injection of trusted JS code by replacing jscall tags with the proper JS.
        /// </summary>
        /// <param name="s">HTML code.</param>
        /// <returns>The clean version of the provided HTML code.</returns>
        public static string RemoveEventsAndScripts(this string s)
        {
            var doc = new HtmlDocument();
            doc.LoadHtml(s);
            var jsCalls = new List<Dictionary<string, string>>(); //A list of calls made to allowed Javascript code registered within the Security Module
            RemoveEventsAndScripts(doc.DocumentNode, jsCalls);
            var body = doc.DocumentNode.SelectSingleNode("//body");
            if (body != null)
            {
                var uniqueFunctions = jsCalls.Where(jsCall => jsCall.ContainsKey("func"))
                                             .Select(jsCall => jsCall["func"]).Distinct();
                foreach (var uniqueFunction in
                    uniqueFunctions.Where(uniqueFunction => RequiredJSCode.ContainsKey(uniqueFunction)))
                {
                    InjectJS(body, RequiredJSCode[uniqueFunction].Code);
                    if (RequiredJSCode[uniqueFunction].References != null)
                        foreach (var reference in RequiredJSCode[uniqueFunction].References)
                        {
                            InjectJSRef(body, reference);
                        }
                }
                var scriptTag = body.OwnerDocument.CreateElement("script");
                scriptTag.Attributes.Add("type", "text/javascript");
                var scriptContents = "";
                foreach (var jsCall in jsCalls.Where(call => call.ContainsKey("func")))
                {
                    var funcName = jsCall["func"];
                    if (OnPageLoadJSFuncs.ContainsKey(funcName))
                    {
                        scriptContents = string.Format("{0}\n{1}", scriptContents, OnPageLoadJSFuncs[funcName](jsCall));
                    }
                }
                scriptTag.AppendChild(
                    doc.CreateTextNode(string.Format("$(document).ready(function () {{\n{0}}});", scriptContents)));
                body.ChildNodes.Add(scriptTag);
                foreach (var jsCall in jsCalls.Where(call => call.ContainsKey("func")))
                {
                    var funcName = jsCall["func"];
                    if (FullJSCode.ContainsKey(funcName))
                    {
                        var scriptTag2 = doc.CreateElement("script");
                        scriptTag2.Attributes.Add("type", "text/javascript");
                        scriptTag2.AppendChild(doc.CreateTextNode(FullJSCode[funcName](jsCall)));
                        body.ChildNodes.Add(scriptTag2);
                    }
                }
            }
            using (var sw = new StringWriter())
            {
                doc.Save(sw);
                return sw.ToString();
            }
        }

        /// <summary>
        /// A recursive method that removes child script tags with custom JS code, child script tags with source link not from <see cref="WhitelistedScriptSources"/> and HTML attributes starting with "on". It also fills <paramref name="jsCalls"/> dictionary with jscall tags found as direct children of <paramref name="node"/> then removes the jscall tag.
        /// </summary>
        /// <param name="node">HTML node to analyze.</param>
        /// <param name="jsCalls">A dictionary of data for jscalls to fill.</param>
        static void RemoveEventsAndScripts(HtmlNode node, List<Dictionary<string, string>> jsCalls)
        {
            if (node.HasAttributes)
                for (var i = 0; i < node.Attributes.Count; i++)
                {
                    if (!node.Attributes[i].Name.StartsWith("on")) continue;
                    node.Attributes.RemoveAt(i);
                    i--;
                }
            for (var i = 0; i < node.ChildNodes.Count; i++)
            {
                if (node.ChildNodes[i].Name.ToLower().Equals("script"))
                {
                    if (node.ChildNodes[i].Attributes["src"] != null &&
                        WhitelistedScriptSources.Any(
                            src => node.ChildNodes[i].Attributes["src"].Value.ToLower().StartsWith(src)))
                        continue;
                    node.ChildNodes.RemoveAt(i);
                    i--;
                }
                else if (node.ChildNodes[i].Name.ToLower().Equals("jscall"))
                {
                    var jsCall =
                        node.ChildNodes[i].Attributes.Where(att => att.Name.ToLower().StartsWith("data-"))
                                          .ToDictionary(att => att.Name.Substring(5).ToLower(), att => att.Value);
                    jsCall.Add("content", node.ChildNodes[i].InnerHtml);
                    jsCalls.Add(jsCall);
                    node.ChildNodes.RemoveAt(i);
                    i--;
                }
                else
                    RemoveEventsAndScripts(node.ChildNodes[i], jsCalls);
            }
        }

        /// <summary>
        /// Injects a script tag with <paramref name="contents"/> as the inside JS code.
        /// </summary>
        /// <param name="body">HTML body tag to inject the script tag in.</param>
        /// <param name="contents">The JS code to be place inside the script tag, must be trusted.</param>
        static void InjectJS(HtmlNode body, string contents)
        {
            var scriptTag = body.OwnerDocument.CreateElement("script");
            scriptTag.Attributes.Add("type", "text/javascript");
            scriptTag.AppendChild(body.OwnerDocument.CreateTextNode(string.Format("\n{0}\n", contents)));
            body.ChildNodes.Add(scriptTag);
        }

        /// <summary>
        /// Injects a script tag with src referring to <paramref name="url"/>.
        /// </summary>
        /// <param name="body">HTML body tag to inject the script tag in.</param>
        /// <param name="url">The URL to the external JS file, must be trusted.</param>
        static void InjectJSRef(HtmlNode body, string url)
        {
            var scriptTag = body.OwnerDocument.CreateElement("script");
            scriptTag.Attributes.Add("src", url);
            body.ChildNodes.Add(scriptTag);
        }

        /// <summary>
        /// A dictionary of jscall functions and handlers to generate code for, this code will be injected in the page load event. All code generated by the handlers specified here will be combined in a single JS function.
        /// </summary>
        static readonly Dictionary<string, Func<Dictionary<string, string>, string>> OnPageLoadJSFuncs = new Dictionary
            <string, Func<Dictionary<string, string>, string>>
            {
                {"slider", Slider},
                {"enable-apply-btn-on-change", EnableApplyBtnOnChange},
                {"enable-apply-btn-on-slider-change", EnableApplyBtnOnSliderChange},
                {"disable-apply-btn", DisableApplyBtn},
                {"tooltip", Tooltip},
                {"submitonreturn", SubmitOnReturn},
                {"tablesorter", TableSorter},
                {"calc-missing-remittance",RecalculateMissingAlerts},
                {"fill-dismiss-alert-modal", FillDismissAlertModal},
                {"search-filter", SearchFilter}
            };

        /// <summary>
        /// A dictionary of jscall functions and handlers to generate code for, this code will be injected as is, each call will have its own script.
        /// </summary>
        static readonly Dictionary<string, Func<Dictionary<string, string>, string>> FullJSCode = new Dictionary
            <string, Func<Dictionary<string, string>, string>>
            {
                {"google-chart", GoogleChart},
                {"fill-modal-input", FillModalInput},
                {"swallow-return-key", SwallowReturnKey},
                {"show-hide-effective-date",ShowOrHideEffectiveDate},
                {"show-after-loading",ShowAfterLoading},
                {"pwd-strength-meter", PasswordStrengtMeter}
            };

        /// <summary>
        /// A simple structure to define JS code to be included in the page if at least 1 call of a matching jscall is found (defined here <see cref="JavascriptManager.RequiredJSCode"/>).
        /// </summary>
        private class RequiredJS
        {
            public string Code { get; set; }
            public string[] References { get; set; }
        }

        /// <summary>
        /// Defines the JS code to include in a page if at least 1 jscall with function matching the key is found.
        /// </summary>
        private static readonly Dictionary<string, RequiredJS> RequiredJSCode = new Dictionary<string, RequiredJS>
            {
                {
                    "slider",
                    new RequiredJS
                        {
                            Code =
                                @"function initSliderCustomRange(sliderName, min, max, initStart, initEnd) {
    $('#' + sliderName).rangeSlider({
        bounds: { min: min, max: max },
        defaultValues: { min: initStart, max: initEnd },
        step: 1,
        arrows: false
    });

    $('#' + sliderName + '_Start').val(initStart);
    $('#' + sliderName + '_End').val(initEnd);

    $('#label_start_' + sliderName).html(min);
    $('#label_end_' + sliderName).html(max);
    $('#label_start_' + sliderName).css({ ""left"": '0px', 'color': '#999999' });
    $('#label_end_' + sliderName).css({ ""right"": '0px', 'color': '#999999' });

    $('#' + sliderName).bind(""valuesChanged"", function (e, data) {
        $('#' + sliderName + '_Start').val(data.values.min);
        $('#' + sliderName + '_End').val(data.values.max);
    });
}

function initSliderMonths(sliderName, initStart, initEnd) {
    var displayValues = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    var min = 1;
    var max = 12;

    $('#' + sliderName).rangeSlider({
        bounds: { min: min, max: max },
        defaultValues: { min: initStart, max: initEnd },
        step: 1,
        arrows: false
    });

    $('#' + sliderName + '_Start').val(initStart);
    $('#' + sliderName + '_End').val(initEnd);

    $('#label_start_' + sliderName).html(displayValues[min-1]);
    $('#label_end_' + sliderName).html(displayValues[max-1]);
    $('#label_start_' + sliderName).css({ ""left"": '0px', 'color': '#999999' });
    $('#label_end_' + sliderName).css({ ""right"": '0px', 'color': '#999999' });


    $('#' + sliderName).on(""valuesChanged"", function (e, data) {
        $('#' + sliderName + '_Start').val(data.values.min);
        $('#' + sliderName + '_End').val(data.values.max);

        $('#MonthRange .ui-rangeSlider-leftLabel .ui-rangeSlider-label-value').text(displayValues[data.values.min - 1]);
        $('#MonthRange .ui-rangeSlider-rightLabel .ui-rangeSlider-label-value').text(displayValues[data.values.max - 1]);
    });

    $('#MonthRange .ui-rangeSlider-leftLabel .ui-rangeSlider-label-value').addClass('hide');
    var leftLabel = $(""<div class='ui-rangeSlider-label-value'>"" + displayValues[initStart - 1] + ""</div>"")
    $(leftLabel).insertAfter('#MonthRange .ui-rangeSlider-leftLabel .ui-rangeSlider-label-value')

    $('#MonthRange .ui-rangeSlider-rightLabel .ui-rangeSlider-label-value').addClass('hide');
    var leftLabel = $(""<div class='ui-rangeSlider-label-value'>"" + displayValues[initEnd - 1] + ""</div>"")
    $(leftLabel).insertAfter('#MonthRange .ui-rangeSlider-rightLabel .ui-rangeSlider-label-value')

    $('#' + sliderName).on(""valuesChanging"", function (e, data) {
        $('#MonthRange .ui-rangeSlider-leftLabel .ui-rangeSlider-label-value').text(displayValues[data.values.min - 1]);
        $('#MonthRange .ui-rangeSlider-rightLabel .ui-rangeSlider-label-value').text(displayValues[data.values.max - 1]);
    });
}",
                            References = new[]
                                {
                                    "https://cdnjs.cloudflare.com/ajax/libs/jQRangeSlider/5.7.2/jQRangeSliderMouseTouch.min.js"
                                    ,
                                    "https://cdnjs.cloudflare.com/ajax/libs/jQRangeSlider/5.7.2/jQRangeSliderDraggable.min.js"
                                    ,
                                    "https://cdnjs.cloudflare.com/ajax/libs/jQRangeSlider/5.7.2/jQRangeSliderHandle.min.js"
                                    ,
                                    "https://cdnjs.cloudflare.com/ajax/libs/jQRangeSlider/5.7.2/jQRangeSliderBar.min.js"
                                    ,
                                    "https://cdnjs.cloudflare.com/ajax/libs/jQRangeSlider/5.7.2/jQRangeSliderLabel.min.js"
                                    ,
                                    "https://cdnjs.cloudflare.com/ajax/libs/jQRangeSlider/5.7.0/jQRangeSlider.min.js"
                                }
                        }
                },
                {
                    "enable-apply-btn-on-change",
                    new RequiredJS
                        {
                            Code = "function enableApplyBtn() { $('#btn_apply').prop(\"disabled\", false); }",
                            References = new string[] {}
                        }
                },
                {
                    "google-chart",
                    new RequiredJS
                        {
                            Code = "",
                            References = new[] {"https://www.google.com/jsapi"}
                        }
                },
                {
                    "tablesorter",
                    new RequiredJS
                        {
                            Code = @"$.tablesorter.addParser({
    id: 'contribution',
    is: function (s) {
        return false;
    },
    format: function (s) {
        return s.replace(/,/g, '').replace('$', '');
    },
    type: 'numeric'
});",
                            References =
                                new[]
                                    {
                                        "https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.26.5/js/jquery.tablesorter.min.js"
                                    }
                        }
                },
                {
                    "pwd-strength-meter",
                    new RequiredJS
                        {
                            Code = @"var strengthClass = {
    1: ""very-weak"",
    2: ""weak"",
    3: ""below-medium"",
    4: ""medium"",
    5: ""above-medium"",
    6: ""strong"",
};
var validatePassword = {
    minLength: false,
    hasLowerChar: false,
    hasUpperChar: false,
    hasNumber: false,
    hasSymbol: false,
    noIntersection: false
};

function strengthMeter(id, username) {
    $(id).change(function () {
        var password = $(id).val();
        //var result = zxcvbn(password);
        var result = strengthEvaluation(password, username);

        // Update the password strength meter
        if (result != 0) {
            result--;
            $.each(validatePassword, function (key, value) {
                if (value)
                    result++;
            });
        }

        $('#password-strength-meter').val(result);

        var message = """";
        if (result == 6)
            message = "" <div class=\""pass\"">Passed <i class=\""fa fa-smile-o\""></i></div>"";
        else {
            message = ""Failed <i class= \""fa fa-frown-o\""></i> Password must satisfy following rules:"";
            message += ""<ul><li>"";
            if (validatePassword.minLength)
                message += ""<i class=\""fa fa-check\""></i>"";
            else
                message += ""<i class=\""fa fa-times\""></i>"";
            message += "" at least 6 characters long</li><li>"";

            if (validatePassword.hasLowerChar)
                message += ""<i class=\""fa fa-check\""></i>"";
            else
                message += ""<i class=\""fa fa-times\""></i>"";
            message += "" one lower alpha character</li><li>"";

            if (validatePassword.hasUpperChar)
                message += ""<i class=\""fa fa-check\""></i>"";
            else
                message += ""<i class=\""fa fa-times\""></i>"";
            message += "" one upper case alpha character</li><li>"";

            if (validatePassword.hasNumber)
                message += ""<i class=\""fa fa-check\""></i>"";
            else
                message += ""<i class=\""fa fa-times\""></i>"";
            message += "" one number</li><li>"";

            if (validatePassword.hasSymbol)
                message += ""<i class=\""fa fa-check\""></i>"";
            else
                message += ""<i class=\""fa fa-times\""></i>"";
            message += "" one symbol</li><li>"";

            if (validatePassword.noIntersection)
                message += ""<i class=\""fa fa-check\""></i>"";
            else
                message += ""<i class=\""fa fa-times\""></i>"";
            message += "" cannot contain 4 or more consecutive characters from Email</li></ul>"";
        }

        // Update the text indicator
        if (password !== """") {
            var innerHtml = ""<div style='padding-left: 10px; padding-top: 10px'><strong>"" + message + ""</strong></div>"" + ""<span class='feedback'>"" + ""</span>"";
            $('#password-strength-text').removeClass();
            $('#password-strength-text').addClass(strengthClass[result]);
            $('#password-strength-text').html(innerHtml);
        }
        else {
            $('#password-strength-text').html('');
        }
    });
}

function strengthEvaluation(password, username) {

    //reset validatePassword
    $.each(validatePassword, function (key, value) {
        validatePassword[key] = false;
    });

    if (password == null || password.length == 0)
        return 0;

    var minLength = 6;
    var hasLowerChar = /^(?=.*[a-z]).+$/;
    var hasUpperChar = /^(?=.*[A-Z]).+$/;
    var hasNumber = /^(?=.*\d).+$/;
    var hasSymbol = /^(?=.*(_|[^\w])).+$/;
    if (password.length >= minLength)
        validatePassword.minLength = true;

    if (hasLowerChar.test(password))
        validatePassword.hasLowerChar = true;

    if (hasUpperChar.test(password))
        validatePassword.hasUpperChar = true;

    if (hasNumber.test(password))
        validatePassword.hasNumber = true;

    if (hasSymbol.test(password))
        validatePassword.hasSymbol = true;

    validatePassword.noIntersection = (lcs(username, password)<4);

    return 1;
}

function lcs(str1, str2) {
    if (str1 == null || str1.length === 0 || str2 == null || str2.length === 0) {
        return 0;
    }

    var num = new Array(str1.length);
    for (i = 0; i < str1.length; i++)
        num[i] = new Array(str2.length);
    var maxlen = 0;

    for (var i = 0; i < str1.length; i++) {
        for (var j = 0; j < str2.length; j++) {
            if (str1[i] !== str2[j]) {
                num[i][j] = 0;
            } else {
                if ((i === 0) || (j === 0)) {
                    num[i][j] = 1;
                } else {
                    num[i][j] = 1 + num[i - 1][j - 1];
                }

                if (num[i][j] > maxlen) {
                    maxlen = num[i][j];
                }
            }
        }
    }
    return maxlen;
}",
                            References = new string[] {}
                        }
                },
                {
                    "bootstrap-toggle", new RequiredJS
                        {
                            Code = "",
                            References =
                                new[] {"https://gitcdn.github.io/bootstrap-toggle/2.2.0/js/bootstrap-toggle.min.js"}
                        }
                },
                {
                    "search-filter",
                    new RequiredJS
                        {
                            Code = @"function insertParam(uri, key, value) {
	var re = new RegExp(""([?&])"" + key + ""=.*?(&|$)"", ""i"");
	var separator = uri.indexOf('?') !== -1 ? ""&"" : ""?"";
	if (uri.match(re)) {
		return uri.replace(re, '$1' + key + ""="" + value + '$2');
	}
	else {
		return uri + separator + key + ""="" + value;
	}
}"
                        }
                }
            };

        /// <summary>
        /// A jscall handler for a range slider.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> id: HTML div id to create the slider. </item>
        /// <item> start: The numeric value to use as the selected start in the slider. </item>
        /// <item> end: The numeric value to use as the selected end in the slider. </item>
        /// <item> type: custom or month, if custom is used then min-value and max-value parameters must be provided. </item>
        /// </list>
        /// </param>
        /// <returns>JS code to be added to page load in order to convert a div to a range slider.</returns>
        private static string Slider(Dictionary<string, string> parameters)
        {
            if (new[] { "id", "start", "end", "type" }.All(parameters.ContainsKey))
            {
                if (parameters["type"].ToLower().Equals("custom"))
                {
                    if (new[] { "min-value", "max-value" }.All(parameters.ContainsKey))
                        return string.Format(@"initSliderCustomRange('{0}',{1},{2},{3},{4});",
                                             parameters["id"],
                                             parameters["min-value"],
                                             parameters["max-value"],
                                             parameters["start"],
                                             parameters["end"]);
                }
                else if (parameters["type"].ToLower().Equals("month"))
                {
                    return string.Format(@"initSliderMonths('{0}',{1},{2});",
                                             parameters["id"],
                                             parameters["start"],
                                             parameters["end"]);
                }
            }
            return "";
        }

        /// <summary>
        /// A jscall handler to make IE submit a form on return (Enter).
        /// </summary>
        /// <param name="parameters">No parameters needed.</param>
        /// <returns>JS code to be injected.</returns>
        private static string SubmitOnReturn(Dictionary<string, string> parameters)
        {
            return "$('form input').keypress(function (e) { if (e.which === 13) { $('form').submit(); return false; } return true; });";
        }

        /// <summary>
        /// A jscall handler to enable button with id btn_apply when the value of the HTML input with the provided id changes, typically this is for graph parameters.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> id: HTML input id of which the change event will be listened to.</item>
        /// </list></param>
        /// <returns>JS code to be injected.</returns>
        private static string EnableApplyBtnOnChange(Dictionary<string, string> parameters)
        {
            if (parameters.ContainsKey("id"))
                return string.Format("$('#{0}').change(enableApplyBtn);", parameters["id"]);
            if (parameters.ContainsKey("name"))
                return string.Format("$(\"input[name='{0}']\").change(enableApplyBtn);", parameters["name"]);
            return "";
        }

        /// <summary>
        /// A jscall handler to enable button with id btn_apply when the selected bounds of the range slider with the provided id changes, typically this is for graph parameters.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> id: Range slider id of which the change event will be listened to.</item>
        /// </list></param>
        /// <returns>JS code to be injected.</returns>
        private static string EnableApplyBtnOnSliderChange(Dictionary<string, string> parameters)
        {
            if (parameters.ContainsKey("id"))
                return string.Format("$('#{0}').on('valuesChanged', enableApplyBtn);", parameters["id"]);
            if (parameters.ContainsKey("name"))
                return string.Format("$(\"input[name='{0}']\").on('valuesChanged', enableApplyBtn);", parameters["name"]);
            return "";
        }

        /// <summary>
        /// A jscall handler to enable bootstrap tooltips.
        /// </summary>
        /// <param name="parameters">No parameters needed.</param>
        /// <returns>JS code to be injected.</returns>
        private static string Tooltip(Dictionary<string, string> parameters)
        {
            return "$('[data-toggle=\"tooltip\"]').tooltip();";
        }

        /// <summary>
        /// A jscall handler to display a Google chart.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> y-label: Y-Axis label.</item>
        /// <item> x-label: X-Axis label.</item>
        /// <item> content: The data table to be fed to Google Charts API.</item>
        /// <item> type: Google Chart type, supported values: ColumnChart, LineChart, PieChart.</item>
        /// </list>
        /// </param>
        /// <returns>JS code to be injected.</returns>
        private static string GoogleChart(Dictionary<string, string> parameters)
        {
            if (new[] { "y-label", "x-label", "content", "type" }.All(parameters.ContainsKey))
            {
                return string.Format(@"google.load('visualization', '1.1', {{ packages: ['corechart'] }});
var options = {{
    vAxis: {{ format: 'currency', title: '{0}' }},
    hAxis: {{ title: '{1}' }},
    tooltip: {{ trigger: 'selection' }},
    is3D: true,
    pointSize: 10,
    colors: ['#009FD0', '#4D4D4D', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#F15854', '#EAD2AC']
}};
google.setOnLoadCallback(drawChart);
function drawChart() {{
    var data = google.visualization.arrayToDataTable([
            {2}
    ]);
    
    var formatter = new google.visualization.NumberFormat({{prefix: '$', fractionDigits:2}});
    for(i=1; i<data.getNumberOfColumns();i++)
    formatter.format(data, i);
    var chart = new google.visualization.{3} (document.getElementById('chartArea'));
    chart.draw(data, options);
    google.visualization.events.addListener(chart, 'onmouseover', function (entry) {{
        chart.setSelection([{{ row: entry.row }}]);
    }});
    google.visualization.events.addListener(chart, 'onmouseout', function (entry) {{
        chart.setSelection([]);
    }});
}}",
                                     parameters["y-label"],
                                     parameters["x-label"],
                                     parameters["content"],
                                     parameters["type"]);
            }
            return "";
        }

        /// <summary>
        /// A jscall handler to disable button with id btn_apply on page load.
        /// </summary>
        /// <param name="parameters">No parameters needed.</param>
        /// <returns>JS code to be injected.</returns>
        private static string DisableApplyBtn(Dictionary<string, string> parameters)
        {
            return string.Format("$('#btn_apply').prop(\"disabled\", true);");
        }

        /// <summary>
        /// A jscall handler to add support for table sorting to the HTML table with the provided id.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> id: HTML table id for which the sort support will be added.</item>
        /// </list></param>
        /// <returns>JS code to be injected.</returns>
        private static string TableSorter(Dictionary<string, string> parameters)
        {
            if (parameters.ContainsKey("id"))
                return string.Format(@"if ($(""#{0}"").length)
                $(""#{0}"").tablesorter({{
                    headers: {{ 1: {{ sorter: 'contribution' }} }},
                    sortMultiSortKey: null
                }});

            $(""thead th"").click(function () {{
                $(this).closest("".header"").removeClass('header');
                $(""thead th"").not(this).each(function () {{
                    if (!$(this).hasClass(""header""))
                        $(this).addClass('header');
                }});
            }});", parameters["id"]);
            return "";
        }

        /// <summary>
        /// A jscall handler to generate JS code to fill a modal with data.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> id: the HTML element containting data to pass.</item>
        /// <item> data-attrib: the part of the name of HTML attibute following "data-", this attribute is the one that contains the data to be passed from HTML element with provided id.</item>
        /// <item> val-id: the HTML element which value should be updated.</item>
        /// <item> html-id: the HTML element which inner HTML should be updated.</item>
        /// </list></param>
        /// <returns>JS code to be injected.</returns>
        private static string FillModalInput(Dictionary<string, string> parameters)
        {
            if (new[] { "id", "data-attrib", "val-id", "html-id" }.All(parameters.ContainsKey))
            {
                return string.Format(@"$('#{0}').on('show.bs.modal', function (event) {{
            var button = $(event.relatedTarget);
            var name = button.data('{1}');
            var modal = $(this);
            modal.find('#{2}').val(name);
            modal.find('#{3}').html(name);
        }});", parameters["id"], parameters["data-attrib"], parameters["val-id"], parameters["html-id"]);
            }
            return "";
        }

        /// <summary>
        /// A jscall handler to generate JS code to prevent the return key from submitting a form.
        /// </summary>
        /// <param name="parameters">
        /// Required parameters:
        /// <list type="bullet">
        /// <item> id: the HTML form.</item>
        /// </list></param>
        /// <returns>JS code to be injected.</returns>
        private static string SwallowReturnKey(Dictionary<string, string> parameters)
        {
            if (parameters.ContainsKey("id"))
                return string.Format(@" $('#{0}').on('keyup keypress', function (e) {{
            var code = e.keyCode || e.which;
            if (code == 13) {{
                e.preventDefault();
                return false;
            }}
            return true;
        }});", parameters["id"]);
            return "";
        }

        private static string ShowOrHideEffectiveDate(Dictionary<string, string> parameters)
        {
            if (new[] { "file-format-id", "effective-date-class" }.All(parameters.ContainsKey))
                return string.Format(@"function showOrHideEffectiveDate() {{
            var visibility = ($('#{0}').val() == ""Address"") ? ""visible"" : ""hidden"";
            $('.{1}').css(""visibility"", visibility);
        }}

        showOrHideEffectiveDate();

        $('#{0}').change(function () {{
            showOrHideEffectiveDate();
        }});", parameters["file-format-id"], parameters["effective-date-class"]);
            return "";
        }

        private static string ShowAfterLoading(Dictionary<string, string> parameters)
        {
            if (parameters.ContainsKey("selector"))
                return string.Format(@"$('{0}').css(""visibility"", ""visible"");", parameters["selector"]);
            return "";
        }

        private static string PasswordStrengtMeter(Dictionary<string, string> parameters)
        {
            if (new[] { "password-id", "username-id" }.All(parameters.ContainsKey))
                return string.Format(@"strengthMeter(""#{0}"", $('#{1}').val());", parameters["password-id"],
                                     parameters["username-id"]);
            return "";
        }

        private static string RecalculateMissingAlerts(Dictionary<string, string> parameters)
        {
            if (new[] { "content" }.All(parameters.ContainsKey))
            {
                return string.Format(@"$.ajax({{
	type: ""post"",
	data: {{ data: {1} }},
	cache: false,
	url: '{0}',
    dataType: 'text',
	error: function(xhr, status, error) {{
	    var err = JSON.parse(xhr.responseText);
	    alert(err.Message);
	}},
	success: function (data, status, xhr) {{
        window.location.replace(""{2}"");
    }}
}});
", "/App/Remittance/StoreCalculatedMissingAlerts".ToAbsoluteUrl(), parameters["content"], "/App/Remittance/Missing".ToAbsoluteUrl());
            }
            return "";
        }

        private static string FillDismissAlertModal(Dictionary<string, string> parameters)
        {
            return string.Format(@"$('.dismiss-alert').click(function () {{
        //alert(JSON.stringify($(this).data()));
$('.dismiss-body').html('<div class=""text-center""><i class=""fa fa-refresh fa-spin fa-3x fa-fw""></i><span class=""sr-only"">Loading...</span></div>');
        $.ajax({{
            type: ""post"",
            data: $(this).data(),
            cache: false,
            url: '{0}',
            dataType: 'text',
            error: function(xhr, status, error) {{
                var err = JSON.parse(xhr.responseText);
                alert(err.Message);
            }},
            success: function (data, status, xhr) {{
                $('.dismiss-body').html(xhr.responseText);
            }}
        }});
    }});", "/App/Remittance/DismissAlert".ToAbsoluteUrl());
        }

        private static string SearchFilter(Dictionary<string, string> parameters)
        {
            if (new[] { "btn-id", "txt-id" }.All(parameters.ContainsKey))
            {
                return string.Format(@"$(""{1}"").keyup(function (event) {{
	if (event.keyCode == 13) {{
		$(""#{0}"").click();
	}}
}});

$('#{0}').click(function () {{
	var uri = window.location.search;
	var link = insertParam(uri, 'page', '1');
	link = insertParam(link, 'filter', encodeURIComponent($('#{1}').val()));
	window.location.href = link;
}});", parameters["btn-id"], parameters["txt-id"]);
            }
            return "";
        }
    }
}
